<template>
  <div class="file-upload">
    <div class="upload-row">
      <div class="upload-area" v-on="events">
        <!--点击上传态-->
        <slot v-if="isUploading" name="loading">
          <button disabled>正在上传</button>
        </slot>
        <!--上传完毕态-->

        <!-- <slot
          name="uploaded"
          v-else-if="lastFileData && lastFileData.loaded"
          :uploadData="lastFileData.data"
        >
          <button disabled>上传成功</button>
        </slot> -->
        <!--默认态-->
        <slot v-else name="default" :uploadData="filesList">
          <span>点击上传</span>
        </slot>
        <input
          ref="fileInput"
          type="file"
          name="file"
          v-bind="$attrs"
          style="display: none"
          @change="handleFileChange"
        />
      </div>
      <div class="upload-trigger">
        <slot name="trigger"></slot>
      </div>
    </div>

    <div class="upload-tip">
      <slot name="tip"> </slot>
    </div>

    <ul>
      <!-- <transition-group name="flip-list" tag="ul"> -->
      <li
        v-for="file in filesList"
        :key="file.uid"
        class="uploaded-file"
        :class="`upload-${file.status}`"
      >
        <!-- <img
          v-if="file.url && listType === 'picture'"
          class="upload-list-thumbnail"
          :src="file.url"
          :alt="file.name"
        /> -->
        <div class="upload-status" :class="`active-${file.status}`"></div>
        <img :src="filePng" class="left-img" />
        <div class="left-wrap" :title="file.name">
          <span class="filname" v-if="file.status !== 'loading'">{{
            file.name
          }}</span>
          <!-- <span class="progress">{{ file.progress }}</span> -->

          <div
            class="loading-box"
            v-if="isUploading && file.status !== 'success'"
          >
            <div
              class="current-loading"
              :style="{ width: file.progress ? file.progress : 0 }"
            ></div>
          </div>
        </div>
        <div class="right-wrap" @click="reomveFile(file)" title="删除">
          <span class="delete-icon">&#8855;</span>
        </div>
      </li>
      <!-- </transition-group> -->
    </ul>
    <!-- <h5 style="color: red">{{ filesList }}</h5> -->
  </div>
</template>
<script lang="ts">
import {
  computed,
  defineComponent,
  nextTick,
  PropType,
  reactive,
  ref,
  watchEffect
} from "vue";
import axios from "axios";
import { v4 as uuidv4 } from "uuid";
import { last } from "lodash-es";
import filePng from "/@/assets/file.png";
import { storageLocal } from "/@/utils/storage";

type UploadStatus = "ready" | "loading" | "success" | "error";
type fileListType = "text" | "picture";
type CheckUpload = (file) => boolean | Promise<File>;
type RemoveUpload = (file) => boolean | any;
export interface UploadFile {
  uid: string; //文件唯一id
  size: number; //文件大小
  name: string; //文件名称
  status: UploadStatus; //上传状态
  raw: File; //文件
  progress?: string;
  resp?: any; //服务端返回数据
  url?: string; //对应展示的url
}
export default defineComponent({
  name: "Uploader",
  props: {
    action: {
      //url地址
      type: String,
      required: true
    },
    beforeUpload: {
      //上传之前的处理
      type: Function as PropType<CheckUpload>
    },
    beforeRemove: {
      // 删除之前的处理
      type: Function as PropType<RemoveUpload>
    },
    autoUpload: {
      //自动上传
      type: Boolean,
      default: true
    },
    listType: {
      type: String as PropType<fileListType>,
      default: "text"
    },
    dataParams: {
      type: Object,
      default: () => {}
    },
    limit: {
      type: Number,
      default: 3
    },
    dataList: {
      type: Array,
      default: () => []
    }
  },
  emits: ["file-uploaded-success", "file-uploaded-error"],
  setup(props, ctx) {
    // JWT鉴权处理
    let authorization = "";
    if (storageLocal.getItem("info")) {
      authorization = storageLocal.getItem("info")?.accessToken;
    }
    const fileInput = ref<null | HTMLInputElement>(null);
    const fileStatus = ref<UploadStatus>("ready"); //存储上传的文件
    const filesList = ref<UploadFile[]>([]);
    //constisDragOver=ref<boolean>(false);
    const triggerUpload = () => {
      if (fileInput.value) {
        fileInput.value.click();
      }
    };
    const events: { [key: string]: () => void } = {
      click: triggerUpload
    };
    // 默认上传的文件
    watchEffect(() => {
      nextTick(() => {
        if (props.dataList) {
          defaultToList(props.dataList);
        }
      });
    });
    //只要有一个文件状态是loading态就说明是正在上传

    const isUploading = computed(() => {
      return filesList.value.some(file => file.status === "loading");
    });
    //获取上传文件的最后一项
    const lastFileData = computed(() => {
      const lastFile = last(filesList.value);
      if (!lastFile) return false;
      return {
        loaded: lastFile?.status === "success",
        data: lastFile?.resp
      };
    }); //删除文件

    function removeFun(obj) {
      filesList.value = filesList.value.filter(
        // @ts-ignore
        file => file.uid !== obj.uid
      );
    }
    const reomveFile = async (fileObj: object) => {
      if (props.beforeRemove) {
        const result = props.beforeRemove(fileObj); //如果有返回值
        if (result && result instanceof Promise) {
          result
            .then(res => {
              if (res) {
                removeFun(fileObj);
              }
            })
            .catch(err => {
              console.log(err);
            });
        } else if (result === true) {
          removeFun(fileObj);
        }
      } else {
        removeFun(fileObj);
      }
    };
    const postFile = (readyFile: UploadFile) => {
      const formData = new FormData();
      formData.append("uploadFile", readyFile.raw); //添加其他参数
      if (props.dataParams) {
        Object.keys(props.dataParams).forEach(key => {
          formData.append(key, props.dataParams[key]);
        });
      }
      readyFile.status = "loading"; //选择文件后push到存储对象里面
      axios
        .post(props.action, formData, {
          headers: {
            "Content-Type": "multipart/form-data",
            Authorization: authorization
          },
          onUploadProgress: progressEvent => {
            const complete =
              (((progressEvent.loaded / progressEvent.total) * 100) | 0) + "%";

            readyFile.progress = complete;
          }
        })
        .then(resp => {
          readyFile.resp = resp.data;
          readyFile.status = "success";
          ctx.emit("file-uploaded-success", resp.data);
        })
        .catch(error => {
          readyFile.status = "error";
          filesList.value = filesList.value.filter(
            file => file.status !== "error"
          );

          ctx.emit("file-uploaded-error", error);
        })
        .finally(() => {
          //一张图片重复上传时无法继续上传bug
          if (fileInput.value) {
            fileInput.value.value = "";
          }
        });
    };
    const addFileToList = (uploadedFile: File) => {
      const fileObj = reactive<UploadFile>({
        uid: uuidv4(),
        size: uploadedFile.size,
        name: uploadedFile.name,
        status: "ready",
        raw: uploadedFile
      }); //处理图片格式并显示 //if(props.listType==='picture'){ //try{ //fileObj.url=URL.createObjectURL(uploadedFile) //}catch(err){ //console.error('uploadfileerr',err) //} //constfileReader=newFileReader(); //fileReader.readAsDataURL(uploadedFile); //fileReader.addEventListener('load',()=>{ //fileObj.url=fileReader.resultasstring; //}); //}
      filesList.value.push(fileObj);
      if (props.autoUpload) {
        postFile(fileObj);
      }
    };
    const defaultToList = (defaultData: Object[]) => {
      defaultData.length &&
        defaultData.forEach(item => {
          // @ts-ignore
          item.uid = uuidv4();
          // @ts-ignore
          item.status = "success";
        });
      // @ts-ignore
      filesList.value = [...defaultData];
    };
    const uploadFiles = () => {
      let isLimit = true;
      // 做一些判断，限制文件个数
      if (props.limit) {
        const fileReady = filesList.value.filter(file => {
          return file.status === "ready";
        });
        if (fileReady.length > props.limit) {
          ctx.emit(
            "file-uploaded-error",
            `文件个数超出${props.limit}个限制，请修改`
          );
          isLimit = false;
        }
      }
      isLimit &&
        filesList.value
          .filter(file => file.status === "ready")
          .forEach(readyFile => postFile(readyFile));
    };
    const beforeUploadCheck = (files: null | FileList) => {
      if (files) {
        fileStatus.value = "loading";
        if (files.length > 1) {
          if (props.limit) {
            if (files.length > props.limit) {
              ctx.emit(
                "file-uploaded-error",
                `文件个数超出${props.limit}个限制，请修改`
              );
              return;
            }
          }
          const fileLength = files.length;
          for (let i = 0; i < fileLength; i++) {
            if (props.beforeUpload) {
              const result = props.beforeUpload(files[i]); //如果有返回值
              if (result && result instanceof Promise) {
                result
                  .then(processedFile => {
                    if (processedFile instanceof File) {
                      addFileToList(processedFile);
                    } else {
                      throw new Error(
                        "before Upload promise should return file object"
                      );
                    }
                  })
                  .catch(err => {
                    console.log(err);
                  });
              } else if (result === true) {
                addFileToList(files[i]);
              } else {
                throw new Error(
                  "before Upload promise should return file object"
                );
              }
            } else {
              addFileToList(files[i]);
            }
          }
        } else {
          const uploadedFile = files[0];
          if (props.beforeUpload) {
            const result = props.beforeUpload(uploadedFile); //如果有返回值
            if (result && result instanceof Promise) {
              result
                .then(processedFile => {
                  if (processedFile instanceof File) {
                    addFileToList(processedFile);
                  } else {
                    throw new Error(
                      "before Upload promise should return file object"
                    );
                  }
                })
                .catch(err => {
                  console.log(err);
                });
            } else if (result === true) {
              addFileToList(uploadedFile);
            }
          } else {
            addFileToList(uploadedFile);
          }
        }
      }
    };
    const handleFileChange = (e: Event) => {
      const target = e.target as HTMLInputElement;
      const files = target.files;
      beforeUploadCheck(files);
    };
    return {
      filePng,
      fileInput,
      triggerUpload,
      handleFileChange,
      isUploading,
      filesList,
      reomveFile,
      lastFileData,
      beforeUploadCheck,
      uploadFiles,
      events
    };
  }
});
</script>
<style lang="scss" scoped>
button {
  border: none;
  // width: 80px;
  padding: 0 10px;
  height: 32px;
  text-align: center;
  border-radius: 3px;
  border: 1px solid #e4e7ed;
  color: #c0c4cc;
  cursor: not-allowed;
}
.trigger-btn {
  background-color: #409eff;
  cursor: pointer;
  color: #fff;
}
.upload-row {
  display: flex;
  justify-content: flex-start;
  align-items: center;
}
.upload-trigger {
  margin-left: 15px;
}
.uploaded-file {
  display: flex;
  justify-content: space-between;
  align-items: center;
  /* border: 1px solid gray; */
  border-radius: 5px;
  width: 300px;
  margin: 5px 0;
  padding: 2px 0;
  color: #606266;
  font-size: 14px;
}

.upload-status {
  width: 8px;
  height: 8px;
  margin: 0 5px;
  border-radius: 4px;
}
.active-ready,
.active-loading {
  background: gray;
}
.active-success {
  background: rgb(103, 194, 58);
}
.active-error {
  background: red;
}

.upload-tip {
  padding: 0 5px;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  width: 300px;
  font-size: 14px;
  color: #606266;
  margin: 5px;
}
.uploaded-file:hover {
  /* opacity: 0.3; */
  background-color: #e4e5e7;
}
.left-wrap {
  flex: 1;
  padding: 0 5px;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.left-img {
  width: 16px;
  height: 16px;
}

.right-wrap {
  width: 20px;
  cursor: pointer;
}
.right-wrap:hover {
  color: red;
}

.loading-box {
  // margin-top: 15px;
  float: left;
  width: 200px;
  height: 6px;
  // background-color: #f6f6f6;
  background-color: #e4e5e7;
  border-radius: 5px;
  margin-left: 15px;

  .current-loading {
    height: 6px;
    border-radius: 5px;
    background-color: #409eff;
    /*width: 50%;*/
  }
}

.flip-list-move {
  transition: transform 1s ease;
}
</style>
